package main

import (
	"bufio"
	"fmt"
	"os"

	"gitlab.w2gou.cn/xbc/go-lib/log"
)

type Instruction struct {
	operator uint16
	operand  uint16
}

const (
	opIncDp  = iota // >
	opDecDp         // <
	opIncVal        // +
	opDecVal        // -
	opOut           // .
	opIn            // ,
	opJmpFwd        // [
	opJmpBck        // ]
)

const dataSize int = 65535

func compileBuff(input string) (program []Instruction, err error) {
	var pc, jmpPc uint16 = 0, 0
	jmpStack := make([]uint16, 0)

	for _, c := range input {
		switch c {
		case '>':
			program = append(program, Instruction{opIncDp, 0})
		case '<':
			program = append(program, Instruction{opDecDp, 0})
		case '+':
			program = append(program, Instruction{opIncVal, 0})
		case '-':
			program = append(program, Instruction{opDecVal, 0})
		case '.':
			program = append(program, Instruction{opOut, 0})
		case ',':
			program = append(program, Instruction{opIn, 0})
		case '[':
			program = append(program, Instruction{opJmpFwd, 0})
			jmpStack = append(jmpStack, pc)
		case ']':
			if len(jmpStack) == 0 {
				return nil, fmt.Errorf("compilation error")
			}
			jmpPc = jmpStack[len(jmpStack)-1]
			jmpStack = jmpStack[:len(jmpStack)-1]
			program = append(program, Instruction{opJmpBck, jmpPc})
			program[jmpPc].operand = pc
		default:
			pc--
		}
		pc++
	}
	if len(jmpStack) != 0 {
		return nil, fmt.Errorf("compilation error")
	}
	return
}

var outCount = 0
var data = make([]int16, dataSize)

func executeBuff(program []Instruction) {
	var dataPtr uint16 = 0
	reader := bufio.NewReader(os.Stdin)

	for i := 0; i < len(program); i += 1 {

		testStr := ""

		switch program[i].operator {
		case opIncDp:
			dataPtr++
		case opDecDp:
			dataPtr--
		case opIncVal:
			data[dataPtr]++
		case opDecVal:
			data[dataPtr]--
		case opOut:
			c := fmt.Sprintf("%c", data[dataPtr])
			fmt.Print(c)

			testStr += fmt.Sprintf("o:%v ", c)
			outCount += 1

		case opIn:
			readVal, _ := reader.ReadByte()
			data[dataPtr] = int16(readVal)
		case opJmpFwd:
			if data[dataPtr] == 0 {
				i = int(program[i].operand)
			}
		case opJmpBck:
			if data[dataPtr] > 0 {
				i = int(program[i].operand)
			}
		default:
			log.Info4("parse_e", "未知符号", "v", program[i].operator)
		} // end switch

		for i := 0; i < 6; i += 1 {
			testStr += fmt.Sprintf("%d ", data[i])
		}
		testStr += fmt.Sprintf("p:%v ", dataPtr)

		log.Info2("fuck_in", testStr)

	} // end for
}

func main() {
	defer log.WriteAll()

	/*

		args := os.Args
		if len(args) != 2 {
			fmt.Printf("Usage: %s filename\n", args[0])
			return
		}
		filename := args[1]
		fileContents, err := os.ReadFile(filename)
		if err != nil {
			fmt.Printf("Error reading %s\n", filename)
			return
		}
	*/

	fileContents := "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>."

	//fileContents := "++++++++++"
	//fileContents := "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++."

	program, err := compileBuff(fileContents)
	if err != nil {
		fmt.Println(err)
		return
	}
	executeBuff(program)

	log.Info4("test", "run over", "out", outCount)

	for i := 0; i < 6; i += 1 {
		fmt.Printf("%d ", data[i])
	}
}
